/**
 * @file   read_jpegvideo_thread.cpp
 * @author Pedro Jimenez
 * @date   Fri Nov 14 15:01:18 2008
 * 
 * @brief  This class is used to read the stereo videos in raw format grabbed with the ./capturar program 
 * 
 * 
 */
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>

#include <sys/time.h>
#include <unistd.h>
#include <sys/select.h>

#include "cv.h"
#include "cxcore.h"
#include "highgui.h"
#include "frame_header.hh"

#include "Producer.hpp"
#include "read_jpegvideo_thread.h"

using namespace std;

///will supported formats in version 1. This version, 2.0 only supports raw format
extern char video_formats_strings[][6];

CRead_jpegVideo_Thread::CRead_jpegVideo_Thread (int capacity) : CProducer<T>(capacity)
{
    video_size_out = cvSize(0,0);
    offsetx[0] = offsety[0] = -1;
    m_numcams = 1;
    nseq =0; //start reading from image 0
}
CRead_jpegVideo_Thread::~CRead_jpegVideo_Thread()
{
    stop();
}

 
/**
 * \brief Get the imaga complete name
 * \param seq Is the image name to return
 * \param video is the number of the video or sequence to use, if there are more than one
 * \return a std::string with the complete path and name for the video
 */
std::string CRead_jpegVideo_Thread::get_imageseq_name(int seq, int video /* =0 */)
{
    return get_pathbasename() + format("/%03d.jpg", seq);
}

/** 
 * \brief Initializes the video files, the params of the videos, 
 * and the buffers stored in \c CProducer::m_container circular_buffer
 * 
 * This function must initialize the data which will be contained in the circular_buffer, and push the buffer in by calling  push_buffer(& pItem)
 *
 * This function must finish with a call to 
 * <pre>return \c CProducer<T>::start() </pre>
 * to start the execution of the thread with the producer code.
 * 
 * The code of this function could go on the \c CRead_jpegVideo_Thread::operator()() overridden function as well 
 * @return true if the producer thread could be started correctly 
 */
bool CRead_jpegVideo_Thread::start() 
{
    T simg;
    int i;
    IplImage *tmpimg;

    //init the video sequence
    for (i=0; i<m_numcams; i++)
    {
	//get images size
	if (!(tmpimg = cvLoadImage(get_imageseq_name(0,i).c_str())))
	    return false;
	video_size[i] = cvGetSize(tmpimg);
	readImageSize[i] = video_size[i].width * video_size[i].height;

	printf("Video %d :  width: %d, height: %d\n", i, video_size[i].width, video_size[i].height);

	video_channels = 3;
	cvReleaseImage(&tmpimg);
    }


    if (video_size_out.width == 0)
	video_size_out.width = video_size[0].width;
    if (video_size_out.height == 0)
	video_size_out.height = video_size[0].height;

    nseq = iniframe;
    //change the image size if needed
    for (i=0; i<m_numcams; i++)
    {
	if (video_size_out.width == video_size[i].width && 
	    video_size_out.height ==video_size[i].height )
	    img_not_equal_img_ori[i] = false;
	else
	{
	    img_not_equal_img_ori[i] = true; 
	    if (video_size[i].width > video_size_out.width)
		roi[i].width = roi_out[i].width = video_size_out.width;
	    else
		roi[i].width = roi_out[i].width = video_size[i].width;

	    if (video_size[i].height > video_size_out.height)
		roi[i].height = roi_out[i].height = video_size_out.height;
	    else
		roi[i].height = roi_out[i].height = video_size[i].height;

	    if (offsetx[i] != -1)
		roi[i].x = offsetx[i];
	    else
		roi[i].x = (video_size[i].width - roi[i].width)/2;
	    roi_out[i].x = (video_size_out.width - roi_out[i].width)/2;

	    if (offsety[i] != -1)
		roi[i].y = offsety[i];
	    else
		roi[i].y = (video_size[i].height - roi[i].height)/2;
	    roi_out[i].y = (video_size_out.height - roi_out[i].height)/2;
	}

	img_clone[i] = cvCreateImage(video_size_out, IPL_DEPTH_8U, video_channels);
	img_mono[i] = cvCreateImage(video_size_out, IPL_DEPTH_8U, 1);
    }
    

    //initialization of the iplimage which will full the buffer
    simg.n = 0;
    do {
	for (i=0; i<m_numcams; i++)
	{
	    simg.img[i] = cvCreateImage(video_size_out, IPL_DEPTH_8U, video_channels);
	    cvZero(simg.img[i]);
	    if (img_not_equal_img_ori[i])
	    {
		simg.img_ori[i] = cvCreateImage(video_size[i], IPL_DEPTH_8U, video_channels);
		cvZero(simg.img_ori[i]);
	    }
	    else
		simg.img_ori[i] = simg.img[i];
	}
	simg.n ++;
    } while ( push_buffer(simg) );
    printf("Read Video Thread Started\n");
    return CProducer<T>::start();
}

/*void CRead_jpegVideo_Thread::operator()() 
  {
  CProducer<T>::operator()();
  //return true;
  //return CProducer<T>::start();
  }
*/

/** 
 * \brief Read form disk each video frame and stores it in the circular_buffer. 
 *
 * This function is executed on each loop of the producer thread. Here is where the video data has to be read from file and stored on the circular_buffer so that the 
 * consumer can use it.
 * 
 * @param pbuf empty buffer where to store a new frame of the buffer
 * 
 * @return true to go on with the execution of the producer thread.
 * @return false to stop the thread, if no more frames to read.
 */
bool CRead_jpegVideo_Thread::producer_callback(T* pbuf)
{
    int i;
    IplImage * tmpimg;

    std::string image_name;
    for (i=0; i<m_numcams; i++)
    {
	image_name.assign(get_imageseq_name(nseq, i));
	tmpimg = cvLoadImage(image_name.c_str());
	cvCopy(tmpimg, pbuf->img_ori[i]);
	cvReleaseImage(&tmpimg);
    }
    pbuf->n = nseq;
    nseq++;
    //check if it is the last image
    if (pbuf->img_ori[0] == NULL)
    {
	printf("End of video\n");
	fflush(stdout);
	//para salir del bucle del CProducer, simplemente se devuelve false
	return false;
    }

    //if the output image is not the same size than the original
    for (int i=0; i<m_numcams; i++)
    {
	if (img_not_equal_img_ori[i])
	{
	    cvSetImageROI(pbuf->img[i], roi_out[i]);
	    cvSetImageROI(pbuf->img_ori[i], roi[i]);
	    cvCopy(pbuf->img_ori[i], pbuf->img[i]);
	    cvResetImageROI(pbuf->img[i]);
	    cvResetImageROI(pbuf->img_ori[i]);
	}
    }

    boost::thread::yield();
    //return true si se quiere seguir con el bucle del productor
    return true;
}
/** 
 * \brief stops the execution of the reading thread, and free the buffers of the circular_buffer
 * 
 */
void CRead_jpegVideo_Thread::stop()
{
    CProducer<T>::stop();

    T * pbuf;
    while ((pbuf = pop_buffer()))
    {
	for (int i=0; i<m_numcams; i++)
	{
	    cvReleaseImage(&pbuf->img[i]);
	    if (img_not_equal_img_ori[i])
		cvReleaseImage(&pbuf->img_ori[i]);
	}
    }

    for (int i=0; i<m_numcams; i++)
    {
	cvReleaseImage(&img_clone[i]);
	cvReleaseImage(&img_mono[i]);
    }
    printf("End of video thread\n");	
}
/** 
 * \brief Gets a new frame from the circular_buffer to use it.
 * 
 * This function gets a new from the circular_buffer, where the producer put it. It prints some imformation about the frames
 * @return the video frame
 */
T* CRead_jpegVideo_Thread::consumer()
{
    //gets the next free buffer
    T * pbuf = CProducer<T>::consumer();
    if (!pbuf)  //Threre aren't more frames
	return NULL;

    //do what ever needed

    //return the buffer to be used
    return pbuf;
}

/** 
 * \brief Initialize some params of the video
 * 
 * This function Initialize the file names of the video files, the format and the first frame to read
 * @param filename of the videos. See at the wiki [readvideo] the possibler input formats
 * @param format the format of the video. Only supports "raw" format. If blank, "raw" is assumed. 
 * @param frame the first frame to read
 * 
 * @return true if the initialization was correct.
 */
int CRead_jpegVideo_Thread::init_params(const char *filename, const char * format, int frame)
{
    if(strlen(filename) == 0)
    {
	printf("Not enough videos.\n");
	return (int)READVIDEO_ERROR_FILENAME_ERROR;
    }
    video_name[0].assign(filename);

    video_format = jpeg_video;

    printf("video format: %d - %s\n", video_format, video_formats_strings[video_format]);
    //extrae los nombres de los videos, según el formato indicado

    iniframe = frame;

    //return 0 indica que todo fue bien
    return 0;
}

/** 
 * \brief Returns the base name of the first video file 
 *  
 * @return a constant buffer to the base name of the first video.
 */
std::string CRead_jpegVideo_Thread::get_pathbasename(int n)
{
    return video_name[n].substr(0, video_name[n].find_last_of('.')); 
}

std::string CRead_jpegVideo_Thread::get_pathfile(int n)
{
    return video_name[n].substr(0, video_name[n].find_last_of('/')); 
}

std::string CRead_jpegVideo_Thread::get_filename(int n)
{
    return video_name[n];
}


IplImage * CRead_jpegVideo_Thread::img(int n)
{
    return get_last_used()->img[n];
}

void CRead_jpegVideo_Thread::set_image_size(CvSize size, int x, int y, int x2, int y2)
{
    video_size_out = size;
    offsetx[0] = x;
    offsetx[1] = x2;
    offsety[0] = y;
    offsety[1] = y2;
}

void CRead_jpegVideo_Thread::set_image_size(int width, int height, int x, int y, int x2, int y2)
{
    set_image_size(cvSize(width, height), x, y, x2, y2);
}


IplImage * CRead_jpegVideo_Thread::get_cloneimage(int n)
{
    return img_clone[n];
}

IplImage ** CRead_jpegVideo_Thread::cloneimages(int n)
{
    if (n<0)
    {
	for (int i=0; i<m_numcams; i++)
	    cvCopy(img(i), img_clone[i]);
	return img_clone;
    }
    else
    {
	cvCopy(img(n), img_clone[n]);
	return &img_clone[n];
    }
}

CvSize CRead_jpegVideo_Thread::image_size()
{
    return video_size_out;
}

int CRead_jpegVideo_Thread::numcams()
{
    return m_numcams;
}

IplImage * CRead_jpegVideo_Thread::get_monoimage(int n)
{
    return img_mono[n];
}

IplImage ** CRead_jpegVideo_Thread::monoimages(int n)
{
    if (n<0)
    {
	for (int i=0; i<m_numcams; i++)
	    cvConvertImage(img(i), img_mono[i]);
	return img_mono;
    }
    else
    {
	cvConvertImage(img(n), img_mono[n]);
	return &img_mono[n];
    }
}
